package com.ease.platform.auditlog.support;

import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;

import com.ease.common.util.Assertion;
import com.ease.model.system.AuditLog;
import com.ease.platform.auditlog.annotation.Auditable;
import com.ease.platform.auditlog.management.AuditLogManagementService;
import com.ease.platform.usercontext.support.UserContextSupportInterceptor;
import com.ease.platform.usercontext.util.InterfaceOperationThreadLocal;
import com.ease.platform.usercontext.util.UserContextThreadLocal;
import com.ease.platform.usercontext.vo.UserContext;

public abstract class AbstractAuditLogInterceptor extends UserContextSupportInterceptor implements InitializingBean {

	private AuditLogManagementService auditLogManagementService;

	public abstract AuditLog popluateAuditLog(MethodInvocation invocation, UserContext userContext);

	public Object invoke(MethodInvocation invocation) throws Throwable {
		// if (true) {
		// // no audit log
		// return invocation.proceed();
		// }
		UserContext userContext = UserContextThreadLocal.getUserContext();
		if (null == userContext) {
			userContext = new UserContext();
			UserContextThreadLocal.setUserContext(userContext);
		}
		Assertion.notNull(userContext, "userContext not found");

		AuditLogContext currentAuditLogContext = AuditLogContext.getAuditLogContext();
		InterfaceOperationThreadLocal.setInterfaceOperation(this.getFirstProxiedInterfaceOperation(invocation));
		if (null == currentAuditLogContext) {
			AuditLog auditLog = popluateAuditLog(invocation, userContext);
			//
			AuditLogContext auditLogContext = new AuditLogContext();
			auditLogContext.setAuditLog(auditLog);
			AuditLogContext.setAuditLogContext(auditLogContext);

			// TBD AuditLogger.logDebug(JsonUtil.marshal(getArguments(invocation)));
		}

		try {

			return invocation.proceed();

		} catch (Throwable e) {
			if (null == currentAuditLogContext) {
				logTargeImplementation(invocation);
				auditLogManagementService.auditError(e);
			}
			throw e;
		} finally {
			if (null == currentAuditLogContext) {
				if (isAuditable(invocation)) {
					logTargeImplementation(invocation);
					auditLogManagementService.auditMessage();
				}
				AuditLogContext.remove();
			}
		}
	}

	private boolean isAuditable(MethodInvocation invocation) {
		/* maybe for interface class */
		Method method = invocation.getMethod();
		if (null == method) {
			throw new RuntimeException("Method not found, invocation: " + invocation);
		}
		boolean methodAuditableValue = true;
		if (method.isAnnotationPresent(Auditable.class)) {
			Auditable methodAuditable = method.getAnnotation(Auditable.class);
			methodAuditableValue = methodAuditable.value();
		}
		/* for implementation class */
		Method targetMethod = getTargetMethod(invocation);
		if (null == targetMethod) {
			throw new RuntimeException("Method not found, invocation: " + invocation);
		}
		boolean targetMethodAuditableValue = true;
		if (targetMethod.equals(method)) {
			targetMethodAuditableValue = methodAuditableValue;
		} else {

			if (targetMethod.isAnnotationPresent(Auditable.class)) {
				Auditable targetMethodAuditable = targetMethod.getAnnotation(Auditable.class);
				targetMethodAuditableValue = targetMethodAuditable.value();
			}
		}
		return methodAuditableValue && targetMethodAuditableValue;

	}

	private void logTargeImplementation(MethodInvocation invocation) {
		AuditLog auditLog = AuditLogContext.getAuditLogContext().getAuditLog();
		Assertion.notNull(auditLog, "auditLog not found");
		// for the plugin mechanism also see
		// ImplementationDispatcherFactory.getImplementation()
		if (null == auditLog.getTargetImplementation()) {
			String targetImplementation = getTargetClass(invocation.getThis()).getName();
			// String targetMethod=getTargetMethod(invocation).getName();
			// auditLog.setTargetImplementation(targetImplementation+"."+targetMethod);
			auditLog.setTargetImplementation(targetImplementation);
		}
	}

	public void afterPropertiesSet() throws Exception {
		Assert.notNull(auditLogManagementService, "auditLogManagementService not found");
	}

	/**
	 * @return the auditLogManagementService
	 */
	public AuditLogManagementService getAuditLogManagementService() {
		return auditLogManagementService;
	}

	/**
	 * @param auditLogManagementService
	 *            the auditLogManagementService to set
	 */
	public void setAuditLogManagementService(AuditLogManagementService auditLogManagementService) {
		this.auditLogManagementService = auditLogManagementService;
	}

}
